/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.dubbo.config;

import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.URLBuilder;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.bytecode.Wrapper;
import org.apache.dubbo.common.config.Environment;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.utils.ClassHelper;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.config.context.ConfigManager;
import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker;
import org.apache.dubbo.config.support.Parameter;
import org.apache.dubbo.metadata.integration.MetadataReportService;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.cluster.ConfiguratorFactory;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ProviderModel;
import org.apache.dubbo.rpc.service.GenericService;
import org.apache.dubbo.rpc.support.ProtocolUtils;

import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static org.apache.dubbo.common.Constants.LOCALHOST_VALUE;
import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
import static org.apache.dubbo.common.utils.NetUtils.getLocalHost;
import static org.apache.dubbo.common.utils.NetUtils.isInvalidPort;

/**
 * ServiceConfig
 *
 * @export
 */
public class ServiceConfig<T> extends AbstractServiceConfig {

  private static final long serialVersionUID = 3033787999037024738L;

  /**
   * The {@link Protocol} implementation with adaptive functionality,it will be different in
   * different scenarios. A particular {@link Protocol} implementation is determined by the protocol
   * attribute in the {@link URL}. For example:
   *
   * <li>when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
   * then the protocol is <b>RegistryProtocol</b></li>
   *
   * <li>when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample,
   * then
   * the protocol is <b>DubboProtocol</b></li>
   * <p>
   * Actually，when the {@link ExtensionLoader} init the {@link Protocol} instants,it will
   * automatically wraps two layers, and eventually will get a <b>ProtocolFilterWrapper</b> or
   * <b>ProtocolListenerWrapper</b>
   */
  private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class)
      .getAdaptiveExtension();

  /**
   * A {@link ProxyFactory} implementation that will generate a exported service proxy,the
   * JavassistProxyFactory is its default implementation
   */
  private static final ProxyFactory proxyFactory = ExtensionLoader
      .getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

  /**
   * A random port cache, the different protocols who has no port specified have different random
   * port
   */
  private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();

  /**
   * A delayed exposure service timer
   */
  private static final ScheduledExecutorService delayExportExecutor = Executors
      .newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true));

  /**
   * The urls of the services exported
   */
  private final List<URL> urls = new ArrayList<URL>();

  /**
   * The exported services
   */
  private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

  /**
   * The interface name of the exported service
   */
  private String interfaceName;

  /**
   * The interface class of the exported service
   */
  private Class<?> interfaceClass;

  /**
   * The reference of the interface implementation
   */
  private T ref;

  /**
   * The service name
   */
  private String path;

  /**
   * The method configuration
   */
  private List<MethodConfig> methods;

  /**
   * The provider configuration
   */
  private ProviderConfig provider;

  /**
   * The providerIds
   */
  private String providerIds;

  /**
   * Whether the provider has been exported
   */
  private transient volatile boolean exported;

  /**
   * The flag whether a service has unexported ,if the method unexported is invoked, the value is
   * true
   */
  private transient volatile boolean unexported;

  /**
   * whether it is a GenericService
   */
  private volatile String generic;

  public ServiceConfig() {
  }

  public ServiceConfig(Service service) {
    appendAnnotation(Service.class, service);
    setMethods(MethodConfig.constructMethodConfig(service.methods()));
  }

  @Deprecated
  private static List<ProtocolConfig> convertProviderToProtocol(List<ProviderConfig> providers) {
    if (CollectionUtils.isEmpty(providers)) {
      return null;
    }
    List<ProtocolConfig> protocols = new ArrayList<ProtocolConfig>(providers.size());
    for (ProviderConfig provider : providers) {
      protocols.add(convertProviderToProtocol(provider));
    }
    return protocols;
  }

  @Deprecated
  private static List<ProviderConfig> convertProtocolToProvider(List<ProtocolConfig> protocols) {
    if (CollectionUtils.isEmpty(protocols)) {
      return null;
    }
    List<ProviderConfig> providers = new ArrayList<ProviderConfig>(protocols.size());
    for (ProtocolConfig provider : protocols) {
      providers.add(convertProtocolToProvider(provider));
    }
    return providers;
  }

  @Deprecated
  private static ProtocolConfig convertProviderToProtocol(ProviderConfig provider) {
    ProtocolConfig protocol = new ProtocolConfig();
    protocol.setName(provider.getProtocol().getName());
    protocol.setServer(provider.getServer());
    protocol.setClient(provider.getClient());
    protocol.setCodec(provider.getCodec());
    protocol.setHost(provider.getHost());
    protocol.setPort(provider.getPort());
    protocol.setPath(provider.getPath());
    protocol.setPayload(provider.getPayload());
    protocol.setThreads(provider.getThreads());
    protocol.setParameters(provider.getParameters());
    return protocol;
  }

  @Deprecated
  private static ProviderConfig convertProtocolToProvider(ProtocolConfig protocol) {
    ProviderConfig provider = new ProviderConfig();
    provider.setProtocol(protocol);
    provider.setServer(protocol.getServer());
    provider.setClient(protocol.getClient());
    provider.setCodec(protocol.getCodec());
    provider.setHost(protocol.getHost());
    provider.setPort(protocol.getPort());
    provider.setPath(protocol.getPath());
    provider.setPayload(protocol.getPayload());
    provider.setThreads(protocol.getThreads());
    provider.setParameters(protocol.getParameters());
    return provider;
  }

  private static Integer getRandomPort(String protocol) {
    protocol = protocol.toLowerCase();
    return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE);
  }

  private static void putRandomPort(String protocol, Integer port) {
    protocol = protocol.toLowerCase();
    if (!RANDOM_PORT_MAP.containsKey(protocol)) {
      RANDOM_PORT_MAP.put(protocol, port);
      logger.warn("Use random available port(" + port + ") for protocol " + protocol);
    }
  }

  public URL toUrl() {
    return urls.isEmpty() ? null : urls.iterator().next();
  }

  public List<URL> toUrls() {
    return urls;
  }

  @Parameter(excluded = true)
  public boolean isExported() {
    return exported;
  }

  @Parameter(excluded = true)
  public boolean isUnexported() {
    return unexported;
  }

  public void checkAndUpdateSubConfigs() {
    // Use default configs defined explicitly on global configs
    completeCompoundConfigs();
    // Config Center should always being started first.
    startConfigCenter();
    checkDefault();
    checkApplication();
    checkRegistry();
    checkProtocol();
    this.refresh();
    checkMetadataReport();

    if (StringUtils.isEmpty(interfaceName)) {
      throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
    }

    if (ref instanceof GenericService) {
      interfaceClass = GenericService.class;
      if (StringUtils.isEmpty(generic)) {
        generic = Boolean.TRUE.toString();
      }
    } else {
      try {
        interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
            .getContextClassLoader());
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
      }
      checkInterfaceAndMethods(interfaceClass, methods);
      checkRef();
      generic = Boolean.FALSE.toString();
    }
    if (local != null) {
      if ("true".equals(local)) {
        local = interfaceName + "Local";
      }
      Class<?> localClass;
      try {
        localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
      }
      if (!interfaceClass.isAssignableFrom(localClass)) {
        throw new IllegalStateException(
            "The local implementation class " + localClass.getName() + " not implement interface "
                + interfaceName);
      }
    }
    if (stub != null) {
      if ("true".equals(stub)) {
        stub = interfaceName + "Stub";
      }
      Class<?> stubClass;
      try {
        stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException(e.getMessage(), e);
      }
      if (!interfaceClass.isAssignableFrom(stubClass)) {
        throw new IllegalStateException(
            "The stub implementation class " + stubClass.getName() + " not implement interface "
                + interfaceName);
      }
    }
    checkStubAndLocal(interfaceClass);
    checkMock(interfaceClass);
  }

  public synchronized void export() {
    checkAndUpdateSubConfigs();

    if (!shouldExport()) {
      return;
    }

    if (shouldDelay()) {
      delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
    } else {
      doExport();
    }
  }

  private boolean shouldExport() {
    Boolean shouldExport = getExport();
    if (shouldExport == null && provider != null) {
      shouldExport = provider.getExport();
    }

    // default value is true
    if (shouldExport == null) {
      return true;
    }

    return shouldExport;
  }

  private boolean shouldDelay() {
    Integer delay = getDelay();
    if (delay == null && provider != null) {
      delay = provider.getDelay();
    }
    return delay != null && delay > 0;
  }

  protected synchronized void doExport() {
    if (unexported) {
      throw new IllegalStateException(
          "The service " + interfaceClass.getName() + " has already unexported!");
    }
    if (exported) {
      return;
    }
    exported = true;

    if (StringUtils.isEmpty(path)) {
      path = interfaceName;
    }
    doExportUrls();
  }

  private void checkRef() {
    // reference should not be null, and is the implementation of the given interface
    if (ref == null) {
      throw new IllegalStateException("ref not allow null!");
    }
    if (!interfaceClass.isInstance(ref)) {
      throw new IllegalStateException("The class "
          + ref.getClass().getName() + " unimplemented interface "
          + interfaceClass + "!");
    }
  }

  public synchronized void unexport() {
    if (!exported) {
      return;
    }
    if (unexported) {
      return;
    }
    if (!exporters.isEmpty()) {
      for (Exporter<?> exporter : exporters) {
        try {
          exporter.unexport();
        } catch (Throwable t) {
          logger.warn("Unexpected error occured when unexport " + exporter, t);
        }
      }
      exporters.clear();
    }
    unexported = true;
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private void doExportUrls() {
    List<URL> registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
      String pathKey = URL
          .buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group,
              version);
      ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
      ApplicationModel.initProviderModel(pathKey, providerModel);
      doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
  }

  private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
      name = Constants.DUBBO;
    }

    Map<String, String> map = new HashMap<String, String>();
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
    appendRuntimeParameters(map);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);

    // 这段代码的主要作用是判断配置中对目标接口配置的方法以及方法参数是否与真实的接口的方法和参数匹配
    if (CollectionUtils.isNotEmpty(methods)) {
      for (MethodConfig method : methods) {
        appendParameters(map, method, method.getName());
        String retryKey = method.getName() + ".retry";
        if (map.containsKey(retryKey)) {
          String retryValue = map.remove(retryKey);
          if ("false".equals(retryValue)) {
            map.put(method.getName() + ".retries", "0");
          }
        }

        // 获取配置中的方法参数信息
        List<ArgumentConfig> arguments = method.getArguments();
        if (CollectionUtils.isNotEmpty(arguments)) {
          for (ArgumentConfig argument : arguments) {
            // convert argument type
            if (argument.getType() != null && argument.getType().length() > 0) {
              // 获取真实接口的方法信息
              Method[] methods = interfaceClass.getMethods();
              // visit all methods
              if (methods != null && methods.length > 0) {
                for (int i = 0; i < methods.length; i++) {
                  String methodName = methods[i].getName();
                  // target the method, and get its signature
                  // 判断配置的方法名与真实的方法名是否一致，如果一致，则继续进行方法参数的判断
                  if (methodName.equals(method.getName())) {
                    // 获取真实接口的方法参数信息
                    Class<?>[] argtypes = methods[i].getParameterTypes();
                    // one callback in the method

                    // 如果配置中的参数的index属性有值，说明其指定了该参数在对应的方法中的位置，
                    // 此时可以直接判断呢真实的参数与该配置的参数类型是否一致
                    if (argument.getIndex() != -1) {
                      if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                        // 方法和参数都匹配，则将方法参数的配置信息设置到map中
                        appendParameters(map, argument,
                            method.getName() + "." + argument.getIndex());
                      } else {
                        // 这种情况是配置了方法参数的索引，但是真实方法的该索引位置的参数类型与配置的类型不一致，
                        // 此时需要抛出异常，因为配置有问题
                        throw new IllegalArgumentException(
                            "Argument config error : the index attribute and type attribute not match :index :"
                                + argument.getIndex() + ", type:" + argument.getType());
                      }
                    } else {
                      // 这个分支是方法参数中没有配置index属性，这种情况下直接遍历方法的所有参数，
                      // 判断其与配置的参数类型是否一致
                      // multiple callbacks in the method
                      for (int j = 0; j < argtypes.length; j++) {
                        Class<?> argclazz = argtypes[j];
                        if (argclazz.getName().equals(argument.getType())) {
                          appendParameters(map, argument, method.getName() + "." + j);
                          if (argument.getIndex() != -1 && argument.getIndex() != j) {
                            // 当前分支基本上不会走到这里
                            throw new IllegalArgumentException(
                                "Argument config error : the index attribute and type attribute not match :index :"
                                    + argument.getIndex() + ", type:" + argument.getType());
                          }
                        }
                      }
                    }
                  }
                }
              }
            } else if (argument.getIndex() != -1) {
              // 这个分支是配置的参数类型为空，此时就检查其index属性，不为空的时候才进行处理
              appendParameters(map, argument, method.getName() + "." + argument.getIndex());
            } else {
              // 当配置的参数类型和index都为空的情况下，抛出异常
              throw new IllegalArgumentException(
                  "Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
            }

          }
        }
      } // end of methods for
    }

    if (ProtocolUtils.isGeneric(generic)) {
      map.put(Constants.GENERIC_KEY, generic);
      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    } else {
      String revision = Version.getVersion(interfaceClass, version);
      if (revision != null && revision.length() > 0) {
        map.put("revision", revision);
      }

      // 这里的Wrapper.getWrapper()方法的作用是动态生成一个继承了Wrapper抽象类的代理类，通过这个类对目标接口
      // 进行封装，以进行诸如获取目标类的方法名、获取目标类的属性名、获取和设置目标类的属性、调用目标类的指定方法
      // 等工作，而这些工作的执行方式都通过Wrapper的封装实现了统一
      String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
      if (methods.length == 0) {
        logger.warn("No method found in service interface " + interfaceClass.getName());
        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
      } else {
        map.put(Constants.METHODS_KEY,
            StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
      }
    }
    if (!ConfigUtils.isEmpty(token)) {
      if (ConfigUtils.isDefault(token)) {
        map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
      } else {
        map.put(Constants.TOKEN_KEY, token);
      }
    }
    // export service
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port,
        getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
        .hasExtension(url.getProtocol())) {
      url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
          .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // don't export when none is configured
    if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {

      // export to local if the config is not remote (export to remote only when config is remote)
      // 只要不是指定了只在远程暴露，那么就会在本地暴露
      if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
        // 这里主要是进行本地暴露，所谓的本地暴露指的是在同一个jvm中可以调用当前暴露的服务。
        // 最终是创建一个InjvmExporter，将其放到InjvmProtocol.exporterMap中
        exportLocal(url);
      }
      // export to remote if the config is not local (export to local only when config is local)
      // 只要不是指定只在本地暴露，则会进行远程暴露
      if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
        if (logger.isInfoEnabled()) {
          logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
        }
        if (CollectionUtils.isNotEmpty(registryURLs)) {
          for (URL registryURL : registryURLs) {
            url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY,
                registryURL.getParameter(Constants.DYNAMIC_KEY));
            URL monitorUrl = loadMonitor(registryURL);
            if (monitorUrl != null) {
              url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
            }
            if (logger.isInfoEnabled()) {
              logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url
                  + " to registry " + registryURL);
            }

            // For providers, this is used to enable custom proxy to generate invoker
            String proxy = url.getParameter(Constants.PROXY_KEY);
            if (StringUtils.isNotEmpty(proxy)) {
              registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
            }

            // 这里是通过javassist动态生成一个实现了Wrapper抽象类的子类，这个子类对interfaceClass中的属性
            // 和方法进行了封装，从而对目标实例的调用实现了一个统一的接口。
            // proxyFactory.getInvoker()方法是对这个Wrapper子类的进一步封装，也就是说，最终所有的服务
            // 都将被封装为一个Invoker对象，通过调用其invoke()方法来实现对目标服务的调用
            Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass,
                registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
            DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(
                invoker, this);

            // 需要注意的是，当前Invoker所使用的URL所指定的protocol是registry，也就是说经过适配后，这里的
            // protocol.export()最开始会调用的是RegistryProtocol.export()，在其内部才会最终调用DubboProtocol.export()
            // 这个方法是创建netty服务，建立与zookeeper的连接，并且将当前服务注册到zookeeper中的核心方法
            Exporter<?> exporter = protocol.export(wrapperInvoker);
            exporters.add(exporter);
          }
        } else {
          Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
          DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(
              invoker, this);

          Exporter<?> exporter = protocol.export(wrapperInvoker);
          exporters.add(exporter);
        }
        /**
         * @since 2.7.0
         * ServiceData Store
         */
        MetadataReportService metadataReportService = null;
        if ((metadataReportService = getMetadataReportService()) != null) {
          metadataReportService.publishProvider(url);
        }
      }
    }
    this.urls.add(url);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
      URL local = URLBuilder.from(url)
          .setProtocol(Constants.LOCAL_PROTOCOL)
          .setHost(LOCALHOST_VALUE)
          .setPort(0)
          .build();
      // 在上面组装URL对象的时候，设置的protocol的值为injvm，因而这里所使用的protocol最终是InjvmProtocol
      // 由于injvm是在同一个jvm中暴露当前的服务，因而这里的InjvmProtocol.export()方法最终只是创建一个表征当前
      // 服务的InjvmExporter，然后将其放到InjvmProtocol.exporterMap中
      Exporter<?> exporter = protocol.export(
          proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
      exporters.add(exporter);
      logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
    }
  }

  private Optional<String> getContextPath(ProtocolConfig protocolConfig) {
    String contextPath = protocolConfig.getContextpath();
    if (StringUtils.isEmpty(contextPath) && provider != null) {
      contextPath = provider.getContextpath();
    }
    return Optional.ofNullable(contextPath);
  }

  protected Class getServiceClass(T ref) {
    return ref.getClass();
  }

  /**
   * Register & bind IP address for service provider, can be configured separately. Configuration
   * priority: environment variables -> java system properties -> host property in config file ->
   * /etc/hosts -> default network address -> first available network address
   */
  private String findConfigedHosts(ProtocolConfig protocolConfig, List<URL> registryURLs,
      Map<String, String> map) {
    boolean anyhost = false;

    String hostToBind = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_BIND);

    // if bind ip is not found in environment, keep looking up
    if (StringUtils.isEmpty(hostToBind)) {
      hostToBind = protocolConfig.getHost();
      if (provider != null && StringUtils.isEmpty(hostToBind)) {
        hostToBind = provider.getHost();
      }

      if (StringUtils.isEmpty(hostToBind)) {
        anyhost = true;
        hostToBind = getLocalHost();

        if (StringUtils.isEmpty(hostToBind)) {
          hostToBind = findHostToBindByConnectRegistries(registryURLs);
        }
      }
    }

    map.put(Constants.BIND_IP_KEY, hostToBind);

    // registry ip is not used for bind ip by default
    String hostToRegistry = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_REGISTRY);
    if (StringUtils.isEmpty(hostToRegistry)) {
      // bind ip is used as registry ip by default
      hostToRegistry = hostToBind;
    }

    map.put(Constants.ANYHOST_KEY, String.valueOf(anyhost));

    return hostToRegistry;
  }

  private String findHostToBindByConnectRegistries(List<URL> registryURLs) {
    if (CollectionUtils.isNotEmpty(registryURLs)) {
      for (URL registryURL : registryURLs) {
        if (Constants.MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
          // skip multicast registry since we cannot connect to it via Socket
          continue;
        }
        try (Socket socket = new Socket()) {
          SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
          socket.connect(addr, 1000);
          return socket.getLocalAddress().getHostAddress();
        } catch (Exception e) {
          logger.warn(e.getMessage(), e);
        }
      }
    }
    return null;
  }

  /**
   * Register port and bind port for the provider, can be configured separately Configuration
   * priority: environment variable -> java system properties -> port property in protocol config
   * file -> protocol default port
   */
  private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name,
      Map<String, String> map) {
    Integer portToBind = null;

    // parse bind port from environment
    String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);
    portToBind = parsePort(port);

    // if there's no bind port found from environment, keep looking up.
    if (portToBind == null) {
      portToBind = protocolConfig.getPort();
      if (provider != null && (portToBind == null || portToBind == 0)) {
        portToBind = provider.getPort();
      }
      final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name)
          .getDefaultPort();
      if (portToBind == null || portToBind == 0) {
        portToBind = defaultPort;
      }
      if (portToBind == null || portToBind <= 0) {
        portToBind = getRandomPort(name);
        if (portToBind == null || portToBind < 0) {
          portToBind = getAvailablePort(defaultPort);
          putRandomPort(name, portToBind);
        }
      }
    }

    // save bind port, used as url's key later
    map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));

    // registry port, not used as bind port by default
    String portToRegistryStr = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_REGISTRY);
    Integer portToRegistry = parsePort(portToRegistryStr);
    if (portToRegistry == null) {
      portToRegistry = portToBind;
    }

    return portToRegistry;
  }

  private Integer parsePort(String configPort) {
    Integer port = null;
    if (configPort != null && configPort.length() > 0) {
      try {
        Integer intPort = Integer.parseInt(configPort);
        if (isInvalidPort(intPort)) {
          throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
        }
        port = intPort;
      } catch (Exception e) {
        throw new IllegalArgumentException("Specified invalid port from env value:" + configPort);
      }
    }
    return port;
  }

  private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
    String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
    String port = ConfigUtils.getSystemProperty(protocolPrefix + key);
    if (StringUtils.isEmpty(port)) {
      port = ConfigUtils.getSystemProperty(key);
    }
    return port;
  }

  private void completeCompoundConfigs() {
    if (provider != null) {
      if (application == null) {
        setApplication(provider.getApplication());
      }
      if (module == null) {
        setModule(provider.getModule());
      }
      if (registries == null) {
        setRegistries(provider.getRegistries());
      }
      if (monitor == null) {
        setMonitor(provider.getMonitor());
      }
      if (protocols == null) {
        setProtocols(provider.getProtocols());
      }
      if (configCenter == null) {
        setConfigCenter(provider.getConfigCenter());
      }
    }
    if (module != null) {
      if (registries == null) {
        setRegistries(module.getRegistries());
      }
      if (monitor == null) {
        setMonitor(module.getMonitor());
      }
    }
    if (application != null) {
      if (registries == null) {
        setRegistries(application.getRegistries());
      }
      if (monitor == null) {
        setMonitor(application.getMonitor());
      }
    }
  }

  private void checkDefault() {
    createProviderIfAbsent();
  }

  private void createProviderIfAbsent() {
    if (provider != null) {
      return;
    }
    setProvider(
        ConfigManager.getInstance()
            .getDefaultProvider()
            .orElseGet(() -> {
              ProviderConfig providerConfig = new ProviderConfig();
              providerConfig.refresh();
              return providerConfig;
            })
    );
  }

  private void checkProtocol() {
    if (CollectionUtils.isEmpty(protocols) && provider != null) {
      setProtocols(provider.getProtocols());
    }
    convertProtocolIdsToProtocols();
  }

  private void convertProtocolIdsToProtocols() {
    if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) {
      List<String> configedProtocols = new ArrayList<>();
      configedProtocols.addAll(getSubProperties(Environment.getInstance()
          .getExternalConfigurationMap(), Constants.PROTOCOLS_SUFFIX));
      configedProtocols.addAll(getSubProperties(Environment.getInstance()
          .getAppExternalConfigurationMap(), Constants.PROTOCOLS_SUFFIX));

      protocolIds = String.join(",", configedProtocols);
    }

    if (StringUtils.isEmpty(protocolIds)) {
      if (CollectionUtils.isEmpty(protocols)) {
        setProtocols(
            ConfigManager.getInstance().getDefaultProtocols()
                .filter(CollectionUtils::isNotEmpty)
                .orElseGet(() -> {
                  ProtocolConfig protocolConfig = new ProtocolConfig();
                  protocolConfig.refresh();
                  return Arrays.asList(protocolConfig);
                })
        );
      }
    } else {
      String[] arr = Constants.COMMA_SPLIT_PATTERN.split(protocolIds);
      List<ProtocolConfig> tmpProtocols =
          CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>();
      Arrays.stream(arr).forEach(id -> {
        if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) {
          tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> {
            ProtocolConfig protocolConfig = new ProtocolConfig();
            protocolConfig.setId(id);
            protocolConfig.refresh();
            return protocolConfig;
          }));
        }
      });
      if (tmpProtocols.size() > arr.length) {
        throw new IllegalStateException(
            "Too much protocols found, the protocols comply to this service are :" + protocolIds
                + " but got " + protocols
                .size() + " registries!");
      }
      setProtocols(tmpProtocols);
    }
  }

  public Class<?> getInterfaceClass() {
    if (interfaceClass != null) {
      return interfaceClass;
    }
    if (ref instanceof GenericService) {
      return GenericService.class;
    }
    try {
      if (interfaceName != null && interfaceName.length() > 0) {
        this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
            .getContextClassLoader());
      }
    } catch (ClassNotFoundException t) {
      throw new IllegalStateException(t.getMessage(), t);
    }
    return interfaceClass;
  }

  /**
   * @see #setInterface(Class)
   * @deprecated
   */
  public void setInterfaceClass(Class<?> interfaceClass) {
    setInterface(interfaceClass);
  }

  public String getInterface() {
    return interfaceName;
  }

  public void setInterface(Class<?> interfaceClass) {
    if (interfaceClass != null && !interfaceClass.isInterface()) {
      throw new IllegalStateException(
          "The interface class " + interfaceClass + " is not a interface!");
    }
    this.interfaceClass = interfaceClass;
    setInterface(interfaceClass == null ? null : interfaceClass.getName());
  }

  public void setInterface(String interfaceName) {
    this.interfaceName = interfaceName;
    if (StringUtils.isEmpty(id)) {
      id = interfaceName;
    }
  }

  public T getRef() {
    return ref;
  }

  public void setRef(T ref) {
    this.ref = ref;
  }

  @Parameter(excluded = true)
  public String getPath() {
    return path;
  }

  public void setPath(String path) {
    checkPathName(Constants.PATH_KEY, path);
    this.path = path;
  }

  public List<MethodConfig> getMethods() {
    return methods;
  }

  // ======== Deprecated ========

  @SuppressWarnings("unchecked")
  public void setMethods(List<? extends MethodConfig> methods) {
    this.methods = (List<MethodConfig>) methods;
  }

  public ProviderConfig getProvider() {
    return provider;
  }

  public void setProvider(ProviderConfig provider) {
    ConfigManager.getInstance().addProvider(provider);
    this.provider = provider;
  }

  @Parameter(excluded = true)
  public String getProviderIds() {
    return providerIds;
  }

  public void setProviderIds(String providerIds) {
    this.providerIds = providerIds;
  }

  public String getGeneric() {
    return generic;
  }

  public void setGeneric(String generic) {
    if (StringUtils.isEmpty(generic)) {
      return;
    }
    if (ProtocolUtils.isGeneric(generic)) {
      this.generic = generic;
    } else {
      throw new IllegalArgumentException("Unsupported generic type " + generic);
    }
  }

  @Override
  public void setMock(Boolean mock) {
    throw new IllegalArgumentException("mock doesn't support on provider side");
  }

  @Override
  public void setMock(String mock) {
    throw new IllegalArgumentException("mock doesn't support on provider side");
  }

  public List<URL> getExportedUrls() {
    return urls;
  }

  /**
   * @deprecated Replace to getProtocols()
   */
  @Deprecated
  public List<ProviderConfig> getProviders() {
    return convertProtocolToProvider(protocols);
  }

  /**
   * @deprecated Replace to setProtocols()
   */
  @Deprecated
  public void setProviders(List<ProviderConfig> providers) {
    this.protocols = convertProviderToProtocol(providers);
  }

  @Override
  @Parameter(excluded = true)
  public String getPrefix() {
    return Constants.DUBBO + ".service." + interfaceName;
  }
}
